#include "widget.h"
#include "ui_widget.h"
#include <QDebug>
#include <QProcess>
#include <QFile>
#include <QStringList>
#include <QMessageBox>
#include <QThread>
#include <QCryptographicHash>
#include <QSqlError>
#include <QSharedMemory>

const int  SEG_SIZE = 640000;
const int  MAX_SIZE = 1000000;

extern QString transFileName;
extern QSharedMemory share;

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    statusText = "检查系统更新...";
    ui->label->setText(statusText);

    QString sty = "color: blue;font-size: 12pt;";
    ui->label->setStyleSheet(sty);

    //延时之后关闭窗口
    connect(&tmExit, &QTimer::timeout, this, [=](){
        close();
    });

    //延时之后开始任务
    connect(&tmAction, &QTimer::timeout, this, [=](){
        action();
    });//

    //状态闪动提示
    connect(&tmFlush, &QTimer::timeout, this, [=]()
    {
        if(ui->label->text().isEmpty())
        {
            ui->label->setText(statusText);
        }
        else
        {
            ui->label->setText("");
        }
        qApp->processEvents();
    });

    //连接数据库
    connect(&tm_connect_database, &QTimer::timeout, this, &Widget::connect_database);

    tm_connect_database.start(100);
    qDebug()<<"start timer tm_connect_database.";
}

Widget::~Widget()
{
    delete ui;
    //share.detach();
}

void Widget::connect_database()
{
    tm_connect_database.stop();

    statusText = "正在进行数据库连接...";
    ui->label->setText(statusText);
    qApp->processEvents();

    //读取配置文件
    setIni = new QSettings("clin.ini", QSettings::IniFormat);

    QSqlDatabase db_m = QSqlDatabase::addDatabase("QMYSQL","db_m");

    //MYSQL数据库
    QString host = setIni->value("database/m_hostname").toString();
    QString port = setIni->value("database/m_port").toString();
    QString dbnm = setIni->value("database/m_databasename").toString();
    QString usr = setIni->value("database/m_username").toString();
    QString pswd = setIni->value("database/m_password").toString();

    db_m.setPort(port.toInt());
    db_m.setHostName(host);
    db_m.setPassword(pswd);
    db_m.setUserName(usr);
    db_m.setDatabaseName(dbnm);
    db_m.setConnectOptions("MYSQL_OPT_CONNECT_TIMEOUT=5");

    qDebug()<<"database openning.";
    if(!db_m.open())
    {
        QString tmp = "Can not open database ！ \nPort: " + port;
        tmp = tmp + "\nHost: " + host;
        tmp = tmp + "\nDBName: " + dbnm;
        QMessageBox::information(NULL,"注意", tmp);

        tmExit.start(300);
        database_connected = false;
    }
    else
    {
        statusText = "检查系统更新...";
        tmFlush.start(300);
        database_connected = true;
        qrm = new QSqlQuery(db_m);
        qrmt = new QSqlQuery(db_m);
        tmAction.start(500);
    }

}

void Widget::action()
{
    tmAction.stop();

    if(transFileName.isEmpty())
    {
        //不带参数，从服务器向本地下载
        download();
    }
    else
    {
        //带参启动，本地向服务器上传
        upload();
    }
}

void Widget::upload()
{
    //打开需要传送的文件
    QFile f(transFileName);

    qDebug()<<"transFileName : "<<transFileName;

    if(!f.open(QIODevice::ReadOnly))
    {
        QMessageBox::information(this, "note", "open file failed.");

        tmExit.start(1000);
        return;
    }



    statusText = "open file " + transFileName + " to transport ";
    QByteArray bt = f.readAll();
    f.close();

    //计算文件md5
    QByteArray bt_md5_bin = QCryptographicHash::hash(bt , QCryptographicHash::Md5);

    QString bt_md5_txt = bt_md5_bin.toHex().constData();

    //base64方式传送
    QString btStr = QString::fromLocal8Bit(bt.toBase64());

    int bin_size = bt.size();
    int len64_size = btStr.length();

    QString sq;

    statusText = "send file " + transFileName + " infomation to server ";
    sq = "select ver from com_update_names where filename = '" + transFileName + "'";
    qrm->exec(sq);
    if(qrm->next())
    {
        //文件已经存在
        sq = "update com_update_names set ";
        sq += " ver = ver + 1,";   //版本号
        sq += " oper_date = now(), ";   //操作时间
        sq += " md5 = '" + bt_md5_txt + "',";   //md5
        sq += " bin_size = " + QString::number(bin_size) + ",";   //二进制字节流长度
        sq += " base64_size = " + QString::number(len64_size) ;   //转换base64格式之后长度
        sq += " where filename = '" + transFileName + "'";   //文件名
    }
    else
    {
        //首次传送
        sq = "insert into com_update_names (filename, ver, oper_date, md5, bin_size, base64_size) values ('";
        sq += transFileName + "',";   //文件名
        sq += " 1,";   //版本号
        sq += "now(), '";   //操作时间
        sq += bt_md5_txt + "',";   //md5
        sq += QString::number(bin_size) + ",";   //二进制字节流长度
        sq += QString::number(len64_size) ;   //转换base64格式之后长度
        sq += ")";
    }

    if(!qrm->exec(sq))
    {
        QMessageBox::information(this, "note", "cann't save file infomation. \n" + qrm->lastError().text() );
        tmExit.start(1000);
        return;
    }

    statusText = "send file " + transFileName + " content to server ";

    //清除旧数据
    qrm->exec("delete from com_update_content where filename = '" + transFileName + "'");

    //保存新数据
    if( len64_size < MAX_SIZE )
    {
        //小于 1M 直接插入
        sq = "insert into com_update_content(filename, idx, content) ";
        sq += " values ('" + transFileName + "', 0, '"+btStr+"')";
        if(!qrm->exec(sq))
        {
            QMessageBox::information(this, "note", qrm->lastError().text() );

            tmExit.start(1000);
            return;
        }
    }
    else
    {
        //大于 1M， 拆分为 640K 段
        sq = "insert into com_update_content(filename, idx, content) ";
        sq += " values ('" + transFileName + "', 0, '"+btStr.left(SEG_SIZE)+"')";
        if(!qrm->exec(sq))
        {
            QMessageBox::information(this, "note", qrm->lastError().text() );

            tmExit.start(1000);
            return;
        }
        else
        {
            //按预定长度计算文件应分为多少个片段
            int count = len64_size / SEG_SIZE;     //
            if( len64_size % SEG_SIZE > 0){        //
                count += 1;
            }

            for(int i = 1; i<count; i++)
            {
                int pos = i * SEG_SIZE;
                sq = "insert into com_update_content(filename, idx, content) ";
                sq += " values ('" + transFileName + "', "; //文件名
                sq += QString::number(i) + ", '"; //文件片段索引
                sq += btStr.mid(pos, SEG_SIZE); //文件片段内容
                sq += "')";
                if(!qrm->exec(sq))
                {
                    QMessageBox::information(this, "note", qrm->lastError().text() );

                    tmExit.start(1000);
                    return;
                }
            }
        }
    }


    tmExit.start(1000);

}

void Widget::download()
{

    //读取升级文件列表
    qrm->exec("select filename, md5, ver, bin_size, base64_size, run_after_download, tips "
              "from com_update_names where filename <> 'update.exe'");
    QString filename, md5_remote , run , tips;
    int ver_remote, ver_local, bin_size;

    while(qrm->next())
    {
        filename = qrm->value(0).toString();
        md5_remote = qrm->value(1).toString();
        ver_remote = qrm->value(2).toInt();
        bin_size = qrm->value(3).toInt();
        run = qrm->value(5).toString();
        tips = qrm->value(6).toString();

        ver_local = setIni->value("version/"+filename, 0).toInt();

        if(ver_local < ver_remote)      //本地版本小于远程版本
        {
            statusText = "下载新版本文件 ： " + filename;
            if(!downloadFile(filename, md5_remote, bin_size))
            {
                break;
            }
            setIni->setValue("version/"+filename, ver_remote);
            if(run == "1")
            {
                //the file need run after download.
                QMessageBox::information(NULL, "注意", tips);
                QProcess *proc = new QProcess();
                proc->startDetached(filename);
                QThread::sleep(10);
            }
        }
    }

    QString start_file_name = setIni->value("version/startApp").toString();

    if(!QFile::exists(start_file_name))
    {

        //启动文件缺失, 尝试下载新的启动文件
        statusText = "启动文件缺失, 尝试下载新的启动文件";
        qrm->exec("select filename, md5, bin_size from com_update_names where filename = '"+start_file_name+"'");
        if(qrm->next())
        {
            filename = qrm->value(0).toString();
            md5_remote = qrm->value(1).toString();
            bin_size =  qrm->value(2).toInt();
            if(!downloadFile(filename, md5_remote, bin_size))
            {
                QMessageBox::information(NULL,"注意", "启动文件下载失败！");

                tmExit.start(1000);
                return;
            }

        }
        else
        {
            QMessageBox::information(NULL,"注意", "无法在升级服务器找到指定的启动文件！");

            tmExit.start(1000);
            return;
        }

    }


    tmExit.start(1000);
    QProcess *proc = new QProcess();
    proc->startDetached(start_file_name);

}

bool Widget::downloadFile(QString filename, QString md5_remote, int bin_size)
{
    QString filecontent, md5_combine;

    qrmt->exec("select content from com_update_content where filename = '" + filename + "' order by idx");
    filecontent = "";
    while (qrmt->next())
    {
        filecontent += qrmt->value(0).toString();
    }

    QByteArray bt = QByteArray::fromBase64(filecontent.toLocal8Bit());

    //计算文件md5
    QByteArray bt_md5_bin = QCryptographicHash::hash(bt , QCryptographicHash::Md5);

    md5_combine = bt_md5_bin.toHex().constData();

    if(bin_size != bt.size() ) //文件长度错误
    {
        QMessageBox::information(this,"错误","下载文件 " + filename + " 长度错误！" );
        return false;
    }

    if(md5_combine != md5_remote) //组合后的md5与文件原有md5不一致
    {
        QMessageBox::information(this,"错误","下载文件 " + filename + " 未通过完整性校验！" );
        return false;
    }

    if(QFile::exists(filename + ".bk"))
    {
        //存在旧文件备份
        if(!QFile::remove(filename + ".bk"))   //移除
        {
            QMessageBox::information(this,"错误","移除文件 " + filename + ".bk" + " 发生错误 ！");
            return false;
        }
    }
    if(QFile::exists(filename))
    {
        //当前下载的文件名存在
        if(!QFile::rename(filename, filename + ".bk"))   //改名为备份文件
        {
            QMessageBox::information(this,"错误","生成备份文件 " + filename + ".bk" + " 发生错误 ！");
            return false;
        }
    }

    QFile f(filename);
    if (!f.open(QIODevice::WriteOnly))
    {
        QMessageBox::information(this,"错误","无法创建文件 " + filename + " ！");
        return false;
    }
    qint64 bytesWritten = f.write(bt);
    if (bytesWritten != bin_size)
    {
        QMessageBox::information(this,"错误","文件 " + filename + " 写入错误！");
        return false;
    }
    f.close();

    return true;
}



